package main

import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"
)

// 配置常量（优化超时和重试）
const (
	cacheDirName   = ".lofi_cache"     // 临时缓存目录
	connTimeout    = 60 * time.Second  // 延长连接超时到1分钟
	totalTimeout   = 300 * time.Second // 延长下载总超时到5分钟
	bufferSize     = 8192 * 4          // 32KB大缓冲区，提升下载速度
	maxRetryCount  = 3                 // 增加重试次数到3次
	progressBarLen = 20                // 下载进度条长度
)

// Track 曲目结构
type Track struct {
	Name string
	URL  string
}

// 全局状态
var (
	allTracks         []Track               // 所有曲目
	playedTracks      map[string]bool       // 已播放曲目URL
	remainingTracks   []Track               // 剩余未播放曲目
	cacheDir          string                // 缓存目录路径
	quitChan          = make(chan struct{}) // 退出信号
	quitOnce          sync.Once             // 确保quitChan只关闭一次
	wg                sync.WaitGroup        // 等待组（管理所有goroutine）
	httpClient        *http.Client          // 全局HTTP客户端
	precacheTrack     *Track                // 预缓存好的下一首曲目
	precacheCachePath string                // 预缓存曲目的缓存路径
	precacheMutex     sync.Mutex            // 预缓存互斥锁
	isPrecaching      bool                  // 是否正在预缓存
)

// 初始化HTTP客户端（优化稳定性）
func initHTTPClient() {
	httpClient = &http.Client{
		Timeout: totalTimeout,
		Transport: &http.Transport{
			MaxIdleConns:        20,
			MaxConnsPerHost:     5, // 增加单主机最大连接数
			IdleConnTimeout:     120 * time.Second,
			TLSHandshakeTimeout: 20 * time.Second,
			DisableKeepAlives:   false,
			ForceAttemptHTTP2:   true, // 启用HTTP2，提升下载稳定性
		},
	}
}

// 加载曲目文件
func loadTracks(filename string) ([]Track, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, fmt.Errorf("加载曲目失败: %w", err)
	}
	defer f.Close()

	var tracks []Track
	scanner := bufio.NewScanner(f)
	for i := 1; scanner.Scan(); i++ {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}
		idx := strings.Index(line, "!")
		if idx == -1 {
			return nil, fmt.Errorf("第%d行格式错误（需为'名称!URL'）", i)
		}
		tracks = append(tracks, Track{
			Name: strings.TrimSpace(line[:idx]),
			URL:  strings.TrimSpace(line[idx+1:]),
		})
	}
	return tracks, scanner.Err()
}

// URL转MD5哈希
func urlToHash(url string) string {
	hash := md5.Sum([]byte(url))
	return hex.EncodeToString(hash[:])
}

// 格式化文件大小（B→KB/MB）
func formatFileSize(bytes int64) string {
	if bytes < 1024 {
		return fmt.Sprintf("%d B", bytes)
	} else if bytes < 1024*1024 {
		return fmt.Sprintf("%.1f KB", float64(bytes)/1024)
	}
	return fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024))
}

// 确保缓存目录存在（双重保障）
func ensureCacheDirExists() error {
	// 检查目录是否存在
	if _, err := os.Stat(cacheDir); os.IsNotExist(err) {
		// 创建目录（带权限755，确保用户有读写权限）
		if err := os.MkdirAll(cacheDir, 0755); err != nil {
			return fmt.Errorf("创建缓存目录失败: %w（可能是权限不足，请检查目录权限）", err)
		}
		fmt.Printf("[缓存] 已创建缓存目录：%s\n", cacheDir)
	}
	return nil
}

// 显示下载进度条（区分预缓存和正常下载）
func printDownloadProgress(isPrecache bool, trackName string, downloaded, total int64) {
	prefix := "[下载]"
	if isPrecache {
		prefix = "[预缓存]"
	}

	// 避免进度条刷屏，只在终端输出
	if total <= 0 {
		fmt.Fprintf(os.Stderr, "\r%s 进度：未知大小 | %s | 曲目：%s", prefix, formatFileSize(downloaded), trackName)
		return
	}

	percent := float64(downloaded) / float64(total)
	filled := int(percent * progressBarLen)
	empty := progressBarLen - filled

	bar := fmt.Sprintf("[%s%s]", strings.Repeat("=", filled), strings.Repeat(" ", empty))
	fmt.Fprintf(os.Stderr, "\r%s 进度：%s %.1f%% | %s / %s | 曲目：%s",
		prefix, bar, percent*100, formatFileSize(downloaded), formatFileSize(total), trackName)
}

// 随机选择下一首要播放/预缓存的曲目（无重复）
func selectNextTrack() (Track, bool) {
	precacheMutex.Lock()
	defer precacheMutex.Unlock()

	// 剩余曲目为空则重置
	if len(remainingTracks) == 0 {
		remainingTracks = make([]Track, len(allTracks))
		copy(remainingTracks, allTracks)
		rand.Shuffle(len(remainingTracks), func(i, j int) {
			remainingTracks[i], remainingTracks[j] = remainingTracks[j], remainingTracks[i]
		})
		fmt.Println("\n[播放列表] 所有曲目已播放完毕，重新打乱循环...")
	}

	// 取最后一首（高效删除）
	if len(remainingTracks) == 0 {
		return Track{}, false
	}
	lastIdx := len(remainingTracks) - 1
	nextTrack := remainingTracks[lastIdx]
	remainingTracks = remainingTracks[:lastIdx]
	playedTracks[nextTrack.URL] = true

	return nextTrack, true
}

// Go原生下载（修复缓存目录+退出响应优化）
func downloadTrack(track Track, isPrecache bool) (string, error) {
	// 双重保障：确保缓存目录存在（解决"no such file or directory"）
	if err := ensureCacheDirExists(); err != nil {
		return "", fmt.Errorf("缓存目录异常: %w", err)
	}

	hash := urlToHash(track.URL)
	cachePath := filepath.Join(cacheDir, fmt.Sprintf("%s.mp3", hash))
	tempPath := filepath.Join(cacheDir, fmt.Sprintf("%s.tmp", hash))

	// 检查缓存（避免重复下载）
	if _, err := os.Stat(cachePath); err == nil {
		if isPrecache {
			fmt.Printf("\n[预缓存] 已缓存：%s\n", track.Name)
		} else {
			fmt.Printf("\n[下载] 已缓存：%s\n", track.Name)
		}
		return cachePath, nil
	}

	// 清理旧临时文件
	os.Remove(tempPath)

	// 构建请求（优化请求头，提升兼容性）
	req, err := http.NewRequest("GET", track.URL, nil)
	if err != nil {
		return "", fmt.Errorf("创建请求失败: %w", err)
	}
	req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36")
	req.Header.Set("Accept", "audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3, application/octet-stream")
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Accept-Encoding", "identity") // 禁用压缩，避免解压错误

	// 下载重试逻辑（增加重试次数）
	var resp *http.Response
	for retry := 0; retry < maxRetryCount; retry++ {
		// 检查退出信号（优先退出）
		select {
		case <-quitChan:
			return "", fmt.Errorf("下载被中断（用户退出）")
		default:
		}

		resp, err = httpClient.Do(req)
		if err == nil && resp.StatusCode == http.StatusOK {
			break
		}
		if retry == maxRetryCount-1 {
			status := "未知"
			if resp != nil {
				status = resp.Status
			}
			return "", fmt.Errorf("请求失败（状态码：%s）: %w（已重试%d次）", status, err, maxRetryCount)
		}
		prefix := "[下载]"
		if isPrecache {
			prefix = "[预缓存]"
		}
		fmt.Printf("\n%s 重试下载 %s（第%d次）\n", prefix, track.Name, retry+1)
		time.Sleep(3 * time.Second) // 延长重试间隔到3秒
	}
	defer resp.Body.Close()

	// 创建临时文件（指定权限，避免权限问题）
	outFile, err := os.OpenFile(tempPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
	if err != nil {
		return "", fmt.Errorf("创建临时文件失败: %w（权限不足或目录不存在）", err)
	}
	defer outFile.Close()

	// 下载进度统计
	totalSize := resp.ContentLength
	downloadedSize := int64(0)
	buf := make([]byte, bufferSize)

	// 带进度下载+退出响应（优化退出检测）
	for {
		select {
		case <-quitChan:
			os.Remove(tempPath)
			return "", fmt.Errorf("下载被中断（用户退出）")
		default:
		}

		// 设置读取超时（避免无限阻塞）
		// resp.Body.SetReadDeadline(time.Now().Add(30 * time.Second))
		n, err := resp.Body.Read(buf)
		if n > 0 {
			if _, writeErr := outFile.Write(buf[:n]); writeErr != nil {
				os.Remove(tempPath)
				return "", fmt.Errorf("写入文件失败: %w", writeErr)
			}
			downloadedSize += int64(n)
			printDownloadProgress(isPrecache, track.Name, downloadedSize, totalSize)
		}

		if err == io.EOF {
			break
		}
		if err != nil {
			os.Remove(tempPath)
			return "", fmt.Errorf("读取数据失败: %w", err)
		}
	}

	// 下载完成，换行（输出到stderr，避免污染进度条）
	fmt.Fprintln(os.Stderr)

	// 重命名为正式缓存文件
	if err := os.Rename(tempPath, cachePath); err != nil {
		os.Remove(tempPath)
		return "", fmt.Errorf("缓存文件重命名失败: %w", err)
	}

	return cachePath, nil
}

// 预缓存下一首曲目（加入wg管理，确保退出时能等待）
func precacheNext() {
	wg.Add(1)
	defer wg.Done()

	precacheMutex.Lock()
	if isPrecaching || isQuitClosed() {
		precacheMutex.Unlock()
		return
	}
	isPrecaching = true
	precacheMutex.Unlock()

	defer func() {
		precacheMutex.Lock()
		isPrecaching = false
		precacheMutex.Unlock()
	}()

	// 选择下一首要预缓存的曲目
	nextTrack, ok := selectNextTrack()
	if !ok {
		fmt.Println("\n[预缓存] 无可用曲目，预缓存失败")
		return
	}

	// 下载预缓存曲目（失败则跳过，重新预缓存）
	cachePath, err := downloadTrack(nextTrack, true)
	if err != nil {
		fmt.Printf("\n[预缓存] 下载失败: %v，跳过该曲目，重新预缓存\n", err)
		// 检查退出状态，不退出则继续预缓存
		if !isQuitClosed() {
			go precacheNext()
		}
		return
	}

	// 预缓存成功，存入全局变量
	precacheMutex.Lock()
	precacheTrack = &nextTrack
	precacheCachePath = cachePath
	precacheMutex.Unlock()

	fmt.Printf("\n[预缓存] 完成：%s（下一首自动播放）\n", nextTrack.Name)
}

// 检查quitChan是否已关闭
func isQuitClosed() bool {
	select {
	case <-quitChan:
		return true
	default:
		return false
	}
}

// 播放曲目
func playTrack(track Track, cachePath string) error {
	// 启动预缓存下一首（只有未退出时才预缓存）
	if !isQuitClosed() {
		go precacheNext()
	}

	// 获取曲目时长（优化超时和错误处理）
	cmd := exec.Command("ffprobe",
		"-v", "error",
		"-show_entries", "format=duration",
		"-of", "default=noprint_wrappers=1:nokey=1",
		"-timeout", "10000000", // 10秒超时
		cachePath)
	output, err := cmd.Output()
	if err != nil {
		fmt.Fprintf(os.Stderr, "\r[播放] %s | 进度：--:--/--:--（无法获取时长）", track.Name)
	} else {
		durStr := strings.TrimSpace(string(output))
		dur, err := time.ParseDuration(fmt.Sprintf("%ss", durStr))
		if err == nil {
			totalSec := int(dur.Seconds())
			// 实时显示播放进度
			go func() {
				ticker := time.NewTicker(1 * time.Second)
				defer ticker.Stop()
				for sec := 0; ; sec++ {
					select {
					case <-ticker.C:
						if sec > totalSec {
							return
						}
						mins := sec / 60
						secs := sec % 60
						totalMins := totalSec / 60
						totalSecs := totalSec % 60
						fmt.Fprintf(os.Stderr, "\r[播放] %s | 进度：%02d:%02d/%02d:%02d", track.Name, mins, secs, totalMins, totalSecs)
					case <-quitChan:
						return
					}
				}
			}()
		} else {
			fmt.Fprintf(os.Stderr, "\r[播放] %s | 进度：--:--/--:--（时长解析失败）", track.Name)
		}
	}

	// 启动播放（-nodisp 禁用视频窗口，纯音频）
	ffplayCmd := exec.Command("ffplay", "-hide_banner", "-nodisp", "-loglevel", "error", "-autoexit", cachePath)
	if err := ffplayCmd.Start(); err != nil {
		return fmt.Errorf("播放失败: %w", err)
	}

	// 监听退出信号，终止播放
	go func() {
		<-quitChan
		ffplayCmd.Process.Kill()
	}()

	// 等待播放完成
	if err := ffplayCmd.Wait(); err != nil {
		return fmt.Errorf("播放中断: %w", err)
	}

	fmt.Fprintf(os.Stderr, "\n[播放] 播放完成：%s\n", track.Name)
	// 播放完成后清理当前曲目缓存
	os.Remove(cachePath)
	return nil
}

// 播放下一首（优先使用预缓存）
func playNext() error {
	// 检查退出信号
	if isQuitClosed() {
		return fmt.Errorf("退出信号已收到")
	}

	precacheMutex.Lock()
	// 优先使用预缓存曲目
	if precacheTrack != nil && precacheCachePath != "" {
		nextTrack := *precacheTrack
		nextCachePath := precacheCachePath
		// 清空预缓存，避免重复使用
		precacheTrack = nil
		precacheCachePath = ""
		precacheMutex.Unlock()

		// 播放预缓存曲目
		if err := playTrack(nextTrack, nextCachePath); err != nil {
			fmt.Printf("[错误] 播放失败: %v，跳过该曲目\n", err)
			return playNext()
		}
		return nil
	}
	precacheMutex.Unlock()

	// 无预缓存时，下载当前曲目（仅首次启动或预缓存失败时触发）
	nextTrack, ok := selectNextTrack()
	if !ok {
		return fmt.Errorf("无可用曲目")
	}

	fmt.Printf("\n[下载] 正在下载：%s（首次启动/无预缓存）\n", nextTrack.Name)
	cachePath, err := downloadTrack(nextTrack, false)
	if err != nil {
		fmt.Printf("[错误] 下载失败: %v，跳过该曲目\n", err)
		return playNext()
	}

	// 播放当前下载的曲目
	if err := playTrack(nextTrack, cachePath); err != nil {
		fmt.Printf("[错误] 播放失败: %v，跳过该曲目\n", err)
	}
	return nil
}

// 无限循环播放
func startPlayback() {
	wg.Add(1)
	defer wg.Done()
	for {
		if isQuitClosed() {
			fmt.Println("\n[退出] 播放循环终止")
			return
		}
		if err := playNext(); err != nil {
			if !strings.Contains(err.Error(), "退出") {
				fmt.Printf("[错误] 播放循环异常: %v\n", err)
				time.Sleep(1 * time.Second)
			} else {
				return
			}
		}
	}
}

// 初始化环境
func initEnv() error {
	initHTTPClient()

	// 初始化缓存目录路径（用户主目录下）
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return fmt.Errorf("获取用户主目录失败: %w", err)
	}
	cacheDir = filepath.Join(homeDir, cacheDirName)

	// 首次创建缓存目录（双重保障）
	if err := ensureCacheDirExists(); err != nil {
		return err
	}

	// 初始化随机种子
	rand.Seed(time.Now().UnixNano())

	// 初始化播放状态
	playedTracks = make(map[string]bool)
	remainingTracks = make([]Track, len(allTracks))
	copy(remainingTracks, allTracks)
	rand.Shuffle(len(remainingTracks), func(i, j int) {
		remainingTracks[i], remainingTracks[j] = remainingTracks[j], remainingTracks[i]
	})

	// Ctrl+C退出处理（优化信号捕获和退出逻辑）
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		sig := <-sigChan
		fmt.Fprintf(os.Stderr, "\n\n[退出] 收到信号 %v，正在终止所有进程...\n", sig)
		// 确保只关闭一次quitChan
		quitOnce.Do(func() {
			close(quitChan)
		})
	}()

	return nil
}

// 清理环境（等待所有goroutine结束后再清理）
func cleanEnv() {
	// 等待所有下载、预缓存、播放goroutine结束
	fmt.Println("[退出] 等待所有进程终止...")
	wg.Wait()

	// 关闭HTTP连接池
	if httpClient != nil && httpClient.Transport != nil {
		httpClient.Transport.(*http.Transport).CloseIdleConnections()
	}

	// 清理缓存目录
	if err := os.RemoveAll(cacheDir); err != nil {
		fmt.Printf("[错误] 清理缓存目录失败: %v\n", err)
	} else {
		fmt.Printf("[退出] 已清理缓存目录：%s\n", cacheDir)
	}

	fmt.Println("[退出] 程序成功退出！")
}

func main() {
	// 命令行参数
	trackFile := flag.String("f", "", "曲目文件路径（必填，格式：每行'名称!URL'）")
	flag.Parse()

	// 检查必填参数
	if *trackFile == "" {
		fmt.Println("用法：lofi-cli -f <曲目文件>")
		fmt.Println("示例：lofi-cli -f standardformat.txt")
		os.Exit(1)
	}

	// 加载曲目（先加载再初始化环境）
	tracks, err := loadTracks(*trackFile)
	if err != nil {
		fmt.Printf("[错误] 加载曲目失败: %v\n", err)
		os.Exit(1)
	}
	if len(tracks) == 0 {
		fmt.Println("[错误] 曲目文件为空")
		os.Exit(1)
	}
	allTracks = tracks

	// 初始化环境
	if err := initEnv(); err != nil {
		fmt.Printf("[错误] 初始化失败: %v\n", err)
		os.Exit(1)
	}
	// 退出时清理环境（defer确保一定会执行）
	defer cleanEnv()

	// 启动信息
	fmt.Printf("[初始化] 成功加载 %d 首曲目\n", len(allTracks))
	fmt.Println("[播放] 开始随机无重复无缝播放（按 Ctrl+C 退出）")
	fmt.Println("[提示] 播放当前曲目时自动预缓存下一首，无等待间隙！")
	fmt.Printf("[缓存] 缓存目录：%s\n", cacheDir)

	// 开始播放
	startPlayback()
}
